#include "player.hpp"

#include "cue.hpp"

Player::Player()
{
}

Player::~Player()
{
}

enum Type
{
	PLAYER,
	ENEMY,
	TYPECT
};

static const float RADIUS = 1;
static const float WIDTH = 2 * RADIUS;
static const float STAND_HT = 4;
static const float CROUCH_HT = 3;
static const float WALK_SPEED = 0.125f;
static const float VOLUME = STAND_HT * WIDTH;
static const float INV_VOLUME = 1.f / VOLUME;

//const int Player::HPMAX = 16;
//static const int AMMO0 = 10;
//static const int AMMOMAX = 99;

static const int MAGCTS[TYPECT] =
{
	2,
	3
};

static const int MAGAMMOS[TYPECT] =
{
	8,
	15
};

enum State
{
	STAND,
	CROUCH,
	JUMP,
	USTAIR,
	DSTAIR,
	HURT,
	DEAD,
	STATECT
};

enum Action
{
	NOACTION = -1,
	AIMCAM,
	AIMGUN,
	FIREGUN,
	LOADGUN,
	ACTIONCT
};

static Image* actionimgs[TYPECT][STATECT][ACTIONCT];
static Image* imgs[TYPECT][STATECT];

const static float ACTION_CYC_PER_UP[TYPECT][ACTIONCT] = 
{
	{
		1.f/8,	1.f/16,	1.f/16, 1.f/32
	},
	{
		0,	1.f/8,	1.f/8,	1.f/16
	}
};

const static float STATE_CYC_PER_UP[STATECT] =
{
	1.f/64, 1.f/8, 1.f/64, 1.f/64, 1.f/64, 0.f, 1.f/16
};

const static bool STATE_LOOPS[STATECT] =
{
	true,	false,	false,	true,	true,	false,	false
};

enum SoundN
{
	STEP,
	SPLASHIN,
	SPLASHOUT,
	OUCH,
	ARGH,
	SOUNDCT
};

static Sound* snds[SOUNDCT];

bool Player::Vulnerable()
{
	return Live() && state != HURT;
}

bool Player::Acting()
{
	return action != NOACTION;
}

bool Player::Crouching()
{
	return state == CROUCH;
}

#include "weapon.hpp"

void Player::Init(float x, float y, Cue & cue)
{
	if (cue.IsPlayer())
	{
		type = PLAYER;
		red = blu = 0;
	}
	else
	{
		type = ENEMY;
		red = grn = 0;
	}

	action = nextaction = NOACTION;
	state = STAND;
	actioncycle = 0;
	stcycle = 0;

	hp = 1;
	mag = 0;
	magammo.reserve(MAGCTS[type]);
	magammo.push_back(MAGAMMOS[type] + 1);
	for (int i = 1; i < MAGCTS[type]; ++i)
		magammo.push_back(MAGAMMOS[type]);

	aijoy = Vec2i(0, 0);

	body.size.x = WIDTH;
	body.size.y = STAND_HT;
	body.pos.x = x;
	body.pos.y = y;
	Face(cue.Dir());
	img = imgs[type][STAND];
}

void Player::StateChange(int s)
{
	if (s == state)
		return;

	switch (state)
	{
	case CROUCH:
	case JUMP:
		switch (s)
		{
		case STAND:
			body.pos.y -= (STAND_HT - CROUCH_HT);
		case HURT:
			body.size.y = STAND_HT;
			break;

		default:
			;
		}

	case STAND:
	case HURT:
		switch (s)
		{
		case CROUCH:
		case JUMP:
			body.size.y = CROUCH_HT;
			body.pos.y += (STAND_HT - CROUCH_HT);
			break;

		default:
			;
		}

	default:
		;
	}

	nextstate = state = s;
	stcycle = 0;
}

#include <string>
#include "image.hpp"
#include "sound.hpp"
#include "music.hpp"

bool Player::TOpen(Cue & cue)
{
	int type = ENEMY;
	if (cue.IsPlayer())
		type = PLAYER;

	const char *TYPEDIRS[TYPECT] =
	{
		"player/",
		"enemy/"
	};

	const char *STATESTRS[STATECT] =
	{
		"stand",
		"crouch",
		"jump",
		"stand",
		"stand",
		"hurt",
		"dead",
	};
	
	const char *ACTIONSTRS[STATECT][ACTIONCT] = 
	{
	{"aim"	,	"aim"	,	"fire"	,	"reload"	},
	{"caim"	,	"caim"	,	"cfire"	,	"creload"	},
	{NULL	,	NULL	,	NULL	,	NULL		},
	{"usaim",	"usaim"	,	"usfire",	"usreload"	},
	{"dsaim",	"dsaim"	,	"dsfire",	"dsreload"	},
	{NULL	,	NULL	,	NULL	,	NULL		},
	{NULL	,	NULL	,	NULL	,	NULL		}
	};

	const char *SOUNDSTRS[SOUNDCT] = 
	{
		"player/step",
		"splashin",
		"splashout",
		"player/ouch",
		"player/argh",
	};

	int got = AllImgs.BatchGet(imgs[type], STATECT,
			TYPEDIRS[type], STATESTRS);
	for (int s = 0; s < STATECT; ++s)
		got += AllImgs.BatchGet(actionimgs[type][s], ACTIONCT,
				TYPEDIRS[type], ACTIONSTRS[s]);
	got += AllSnds.BatchGet(snds, SOUNDCT, 0, SOUNDSTRS);

	return true;
}

void Player::Close()
{
}

#include "stage.hpp"

#include "input.hpp"

#include "item.hpp"
#include "rand.hpp"

float Player::Advantage() const
{
	switch (action)
	{
		case AIMGUN:
			return actioncycle;
		case FIREGUN:
			return 1;
	}
	return 0;
}

float Player::AdvantageOver(const Player & other) const
{
	return Advantage() - other.Advantage();
}

void Player::AI()
{
	Vec2f plrcentbot (Plr.body.CentBot());
	dest = plrcentbot;
	Vec2f centbot = body.CentBot();
	Vec2f todest = dest - centbot;
	Vec2i ustair (StairFind(-1));
	Vec2i dstair (StairFind(1));

	int buttons = 0;

	float dirtoplr = (plrcentbot.x > centbot.x) 
		- (plrcentbot.x < centbot.x);

	if (dir == dirtoplr && Plr.Live())
	{
		Box2f boxofsight(WeaponPos(),
				Plr.WeaponPos() - WeaponPos());
		if (!Stg.PropArea(Stg.SOLID, 
					boxofsight.AxMin(X),
					boxofsight.AxMin(Y),
					boxofsight.AxMax(X),
					boxofsight.AxMax(Y)))
		{
			if (std::abs(boxofsight.size.y) < 1)
			{
				if (magammo[mag] > 0)
					buttons |= Inp.CmdBit(Inp.AIMGUN);
				if (action == AIMGUN && actioncycle >= 1)
					buttons |= Inp.CmdBit(Inp.SHOOT);
			}
			//if (Plr.state == CROUCH)
			//	aijoy.y = 1;
		}
	}

	const float AIBOX_SIZE = .25f;
	float x = body.AxCenter(X);
	int col = (int)x;
	//if (aijoy.LenSq() == 0 || int(x + vel.x) != int(x))
	{
		aijoy = Stg.PathNextStep(centbot.x, centbot.y, dest.x, dest.y);
	}

	switch (state)
	{
		case USTAIR:
			if (!aijoy.y)
			{
				aijoy.y = -1;
				aijoy.x = dir;
			}
			break;
                
		case DSTAIR:
			if (!aijoy.y)
			{
				aijoy.y = 1;
				aijoy.x = dir;
			}
			break;

		default:
			if (aijoy.y > 0 && dstair.x < 0)
				aijoy.y = 0;
	}

	In(aijoy, buttons);
}

void Player::In(const Vec2f & joy, int buttons)
{
	const float JUMP_VEL0 = -.5625f;
	const float ACTION_TURNWIN = .5f;

	bool shootbtn = buttons & Inp.CmdBit(Inp.SHOOT);
	bool aimgunbtn = buttons & Inp.CmdBit(Inp.AIMGUN);

	if (state != JUMP && state != HURT && Live())
	{
		if (aimgunbtn)
		{
			if (!Acting() || action == AIMGUN)
			{
				nextaction = AIMGUN;
			}

			if (action == FIREGUN || actioncycle >= 1);
			{
				if (shootbtn)
				{
					ActionChange(FIREGUN);
				}
			}
		}
		else if (shootbtn)
		{
			for (int i = 0; i < magammo.size(); ++i)
			{
				if ((mag + i) % magammo.size() > 0)
				{
					ActionChange(LOADGUN);
					break;
				}
			}
		}
		else if (!Acting() || action == AIMGUN)
		{
			nextaction = NOACTION;
		}
	}
		
	float watervol = Stg.PropArea(Stage::WATER, body);

	if (state == DSTAIR || state == USTAIR)
	{
		Vec2i ustair (StairFind(-1));
		Vec2i dstair (StairFind(1));
		if (dstair.x >= 0)
		{
			int stairtile = Stg.Get(dstair.x, dstair.y);

			if (Stg.PropGet(stairtile, Stg.DRSTAIR))
			{
				if (joy.y)
					vel.y = joy.y;
				else
					vel.y = joy.x;
				vel.x = vel.y;
				vel *= .5f * WALK_SPEED;
				if (actioncycle < ACTION_TURNWIN)
				{
					if (joy.x)
						Face(joy.x);
					else
						Face(vel.x);
				}
				if (dir < 0)
					StateChange(USTAIR);
				else 
					StateChange(DSTAIR);
			}
			else if (Stg.PropGet(stairtile, Stg.URSTAIR))
			{
				if (joy.y)
					vel.y = joy.y;
				else
					vel.y = -joy.x;
				vel.x = -vel.y;
				vel *= .5f * WALK_SPEED;
				if (actioncycle < ACTION_TURNWIN)
				{
					if (joy.x)
						Face(joy.x);
					else
						Face(vel.x);
				}
				if (dir < 0)
					StateChange(DSTAIR);
				else
					StateChange(USTAIR);
			}
		}
		else
		{
			if (vel.y < 0)
				body.pos.y = (int)body.pos.y + 1;
			StateChange(STAND);
		}
	}
	else if (state == STAND)
	{
		vel.x = joy.x * WALK_SPEED;

		if (actioncycle < ACTION_TURNWIN)
			Face(joy.x);

		if (joy.y > 0)
		{
			if (StairTake(joy.y).x > -1)
				vel.x = vel.y = 0;
			else	
			{
				StateChange(CROUCH);
				ActionChange(NOACTION);
			}
		}
		else if (joy.y < 0)
		{
			if (StairTake(joy.y).x > -1)
				vel.x = vel.y = 0;
		}
	}
	else if (state == CROUCH)
	{
		if (actioncycle < ACTION_TURNWIN)
			Face(joy.x);
		if (joy.y <= 0)
			nextstate = STAND;
		else
			nextstate = CROUCH;
	}
}

#include "game.hpp"

Vec2i Player::StairFind(const Vec2f & p, int diry)
{
	Vec2i stair (-1, -1);

	Vec2i botcent (p);
	if (diry < 0)
		--botcent.y;

	for (int c = botcent.x - 1; stair.x < 0 && c <= botcent.x + 1; ++c)
	{
		int tile = Stg.Get(c, botcent.y);

		if (Stg.PropGet(tile, Stg.URSTAIR)
			|| Stg.PropGet(tile, Stg.DRSTAIR))
		{
			stair = Vec2i(c, botcent.y);
		}
	}

	return stair;
}

Vec2i Player::StairTake(int diry)
{
	Vec2i stair (StairFind(diry));

	if (stair.x < 0)
		return stair;

	int stairtile = Stg.Get(stair.x, stair.y);
	int stairstate = DSTAIR;
	float jigger = .125f;

	if (diry < 0)
	{
		stairstate = USTAIR;
		jigger = 1 - jigger;
	}

	if (Stg.PropGet(stairtile, Stg.DRSTAIR))
	{
		body.pos.x = stair.x - body.AxHalf(X) + jigger;
		body.pos.y = stair.y - body.size.y + jigger;
		Face(diry);
		StateChange(stairstate);
	}
	else if (Stg.PropGet(stairtile, Stg.URSTAIR))
	{
		body.pos.x = stair.x - body.AxHalf(X) + 1 - jigger;
		body.pos.y = stair.y - body.size.y + jigger;
		Face(-diry);
		StateChange(stairstate);
	}

	return stair;
}

void Player::Update() 
{
	if (type == ENEMY)
	{
		AI();
	}

	// physics/anim update
		
	const float JUMP_ACCEL = 1.f / 32;
	const float JUMP_TERMVEL = 1;

	float watervol = Stg.PropArea(Stage::WATER, body);

	const float DEAD_COLOR_FADE_PER_UPDATE = 1.f/64;

	switch (state)
	{
	case DEAD:
		stcycle += STATE_CYC_PER_UP[state];
		red = std::min(red + DEAD_COLOR_FADE_PER_UPDATE, 1.f);
		grn = std::min(grn + DEAD_COLOR_FADE_PER_UPDATE, 1.f);
		blu = std::min(blu + DEAD_COLOR_FADE_PER_UPDATE, 1.f);
		break;

	case JUMP:
	case HURT:
		vel.y = std::min(vel.y + JUMP_ACCEL, JUMP_TERMVEL);
		break;

	case STAND:
		vel.y = 0;
	case USTAIR:
	case DSTAIR:
		if (Acting())
			vel.x = vel.y = 0;

		if (vel.x || vel.y)
			stcycle += STATE_CYC_PER_UP[state];
		else
			stcycle = 0;
		break;

	case CROUCH:
		vel.x = vel.y = 0;
		if (nextstate == CROUCH)
		{
			stcycle += STATE_CYC_PER_UP[state];
		}
		else
		{
			ActionChange(NOACTION);
			stcycle = std::min(stcycle, 1.f);
			stcycle -= STATE_CYC_PER_UP[state];
			if (stcycle <= 0)
				StateChange(STAND);
		}
		break;
	
	default:
		;
	}

	// move

	for (int ax = Y; ax > NOAXIS; --ax)
	{
		body.pos[ax] += vel[ax];

		switch (state)
		{
			case USTAIR:
			case DSTAIR:
				break;

			default:
				stghit[ax] = Stg.AxisHit(body, ax);
				if (StgHitting(ax))
					body.pos[ax] -= stghit[ax];
		}
	}

	// state change

	if (state == USTAIR || state == DSTAIR)
	{
		watervol = Stg.PropArea(Stg.WATER, body);
		if (watervol > VOLUME * .75f)
		{
		}
	}
	else if (state == JUMP || state == HURT)
	{
		if (StgHitting(Y))
		{
			if (0 <= stghit.y)
			{
				if (!Live())
				{
					StateChange(DEAD);
					//snd = snds[ARGH];
					vel.x = 0;
				}
				else if (vel.y >= JUMP_TERMVEL)
				{
					StateChange(CROUCH);
					stcycle = -1;
					snd = snds[STEP];
				}
				else
				{
					StateChange(STAND);
				}
			}
			vel.y = 0;
		}

		watervol = Stg.PropArea(Stg.WATER, body);
		if (watervol >= VOLUME * .75f)
		{
			snd = snds[SPLASHIN];
		}
	}
	else if (state == STAND)
	{
		if (!StgHitting(Y))
		{
			vel.x = 0;
			StateChange(JUMP);
		}
		else if (!Acting())
		{
			Vec2f bc (body.CentBot());

			if (!Stg.PropGet(Stg.Get(bc.x, bc.y), Stg.SOLID))
			{
				Vec2i stair (-1, -1);

				for (int diry = -1; 
					diry <= 1 && stair.x < 0; 
					diry += 2)
				{
					stair = StairTake(diry);
					if (stair.x > -1)
						vel.x = vel.y = 0;
				}
			}
		}
		
		watervol = Stg.PropArea(Stg.WATER, body);
		if (watervol >= VOLUME * .5f)
		{
		}
	}

	// action update

	if (action != nextaction)
	{
		actioncycle = std::min(actioncycle, 1.f);
		actioncycle -= ACTION_CYC_PER_UP[type][action];
		if (actioncycle <= 0)
		{
			ActionChange(nextaction);
		}
	}
	else if (Acting())
	{
		if (actioncycle == 0)
		{
			switch (action)
			{
			case FIREGUN:
				if (mag >= 0 && magammo[mag] > 0)
				{
					--magammo[mag];
					Vec2f wpos (WeaponPos());
					Cue wcue(Weapon::BULLET * dir);
					Weapon *w = Wpns.Init(
							wpos.x, wpos.y, wcue);
					Cue brasscue(Item::POINT_S + Cue::ITEM);
					Item *brass = Itms.Init(wpos.x, wpos.y,
							brasscue);
					const unsigned BRASS_VELX_RAND = 4;
					if (brass)
					{
						brass->vel = Vec2f(-dir*.125f,
								-.25f);
						brass->vel.x *= 1 + Rand.Get
							(BRASS_VELX_RAND - 1);
						brass->vel.x /= BRASS_VELX_RAND;
					}
				}
				else
				{
					actioncycle = 1;
					// dry fire
				}
				break;
			}
		}

		actioncycle += ACTION_CYC_PER_UP[type][action];

		if (actioncycle >= 1)
		{
			switch (action)
			{
			case LOADGUN:
				for (int i = 0; i < magammo.size(); ++i)
				{
					int nextmag = (mag + 1) % magammo.size();
					if (magammo[nextmag] > 0)
					{
						if (magammo[mag] > 0)
						{
							--magammo[mag];
							++magammo[nextmag];
						}
						mag = nextmag;
						break;
					}
				}
			case FIREGUN:
				nextaction = action = AIMGUN;
				break;
			}
		}
	}

	// output update
	
	img = Acting() ? actionimgs[type][state][action] : imgs[type][state];
	frame = Acting() ? actioncycle : stcycle;
	if (img)
	{
		frame *= img->TileCt();
		if (Acting() || !STATE_LOOPS[state])
			frame = std::min(frame, -1.f + img->TileCt());
	}
}


void Player::ActionChange(int a)
{	
	actioncycle = 0;
	nextaction = action = a;
}

//#include "enemy.hpp"

void Player::Hit(Weapon & en)
{
	if (Vulnerable())// && en.Vulnerable() && en.Damage())
	{
		ActionChange(NOACTION);
		HPAdd(-en.Damage());

		if (!Live() || 
			(state != USTAIR && state != DSTAIR))
		{
			StateChange(HURT);
			Face(en.body.pos.x - body.pos.x);
			vel.x = -WALK_SPEED * dir;
			vel.y = -.375f;
		}

		snd = snds[ARGH];

		if (!Live())
		{
			Kill();
			//Mus.Stop();
		}
	}
}

void Player::Hit(Item & itm)
{
}

#include "door.hpp"

void Player::Hit(Door & door)
{
	if (door.IsShut())
	{
		Vec2f hit = body.HitVec(door.body);
		body.pos.x -= hit.x;
		stghit.x = hit.x;
	}
}

#include "hud.hpp"

void Player::HUDUpdate()
{
	HUD.Put(HUD.GAUGE_HP, hp);
	//HUD.Put(HUD.NUM_AMMO, ammo);
	HUD.IconSet(HUD.ICON_SHOT, Weapon::NOTYPE);
}

Vec2f Player::WeaponPos()
{
	Vec2f wpos (body.pos);
	wpos.x += body.AxHalf(X);
	wpos.y += STAND_HT * .25f;
	return wpos;
}

void Player::HPAdd(int delta)
{
	hp += delta;
	//hp = std::max(0, std::min(hp + delta, HPMAX));
}

//void Player::AmmoAdd(int delta)
//{
//	ammo = std::max(0, std::min(ammo + delta, AMMOMAX));
//}

Player Plr;
ActPool<Player, 31> Enmes;
